package com.eden.websocket.config;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.eden.common.constant.ChatConstants;
import com.eden.common.utils.DateUtils;
import com.eden.common.utils.StringUtils;
import com.eden.websocket.domain.ChatList;
import com.eden.websocket.domain.Message;
import com.eden.websocket.service.impl.ChatListServiceImpl;
import com.eden.websocket.service.impl.MessageServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.servlet.http.HttpSession;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Component
@ServerEndpoint(value = "/chat/{loginName}", configurator = WebSocketConfig.class)
public class WebSocketServer {

    //
//    //获取全局容器
    private ApplicationContext applicationContext;
    @Autowired
    private MessageServiceImpl messageService;
    @Autowired
    private ChatListServiceImpl chatListService;
    /**
     * 静态变量，用来记录当前在线连接数。应该把它设计成线程安全的。
     */
    private static int onlineCount = 0;
    /**
     * 存放用户不在线的消息集合
     */
    private static ConcurrentHashMap<String, List<Message>> messageMap = new ConcurrentHashMap<>();
    /**
     * 存放推送用户信息
     */
    private static ConcurrentHashMap<String, String> toUserMap = new ConcurrentHashMap<>();
    /**
     * concurrent包的线程安全Set，用来存放每个客户端对应的MyWebSocket对象。
     */
    private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
    /**
     * 与某个客户端的连接会话，需要通过它来给客户端发送数据
     */
    private Session session;
    /**
     * 开启的loginName
     */
    private String loginName = "";

    /**
     * 表示有浏览器链接过来的时候被调用
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("loginName") String loginName, EndpointConfig config) {
        //获取登录时存放httpSession的用户数据
        HttpSession httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
        log.info("现在来连接的客户id：" + session.getId() + "用户名：" + loginName);
        applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(httpSession.getServletContext());
        this.session = session;
        log.info("发送会话session:" + session);
        this.loginName = loginName;
        this.chatListService = (ChatListServiceImpl) applicationContext.getBean("chatListServiceImpl");
        this.messageService = (MessageServiceImpl) applicationContext.getBean("messageServiceImpl");
        if (webSocketMap.containsKey(loginName)) {
            webSocketMap.remove(loginName);
            webSocketMap.put(loginName, this);
            //加入set中
        } else {
            messageMap.put(loginName, new ArrayList<>());
            webSocketMap.put(loginName, this);
            //加入set中
            addOnlineCount();
            //在线数加1
        }
        log.info("用户连接:" + loginName + ",当前在线人数为:" + getOnlineCount());
        try {
            sendMessage(session, "连接成功");
        } catch (IOException e) {
            log.error("用户:" + loginName + ",网络异常!!!!!!");
        }
    }

    /**
     * OnClose 表示浏览器发出关闭请求的时候被调用
     */
    @OnClose
    public void onClose() {
        if (webSocketMap.containsKey(loginName)) {
            //存储消息记录
            saveMsg();
            webSocketMap.remove(loginName);
            //从set中删除
            subOnlineCount();
        }
        log.info("用户退出:" + loginName + ",当前在线人数为:" + getOnlineCount());
    }

    public void saveMsg() {
        int count = 0;
        log.info("用户退出:" + loginName + "开始存储消息记录");
        List<Message> messages = messageMap.get(loginName);
        if (messages.size() != 0) {
            messageService.insertMessageList(messages);
            for (Message message : messages) {
                if (ChatConstants.MSG_NOT_READ == message.getMsgStatus()) {
                    count = count + 1;
                }
            }
            if (count != 0) {
                //更新接收方的消息未读数
                ChatList chatList = new ChatList();
                String toUserLoginName = toUserMap.get(loginName);
                chatList.setChatFromLoginName(toUserLoginName);
                chatList.setChatToLoginName(loginName);
                List<ChatList> chatLists = chatListService.selectChatListList(chatList);
                //查询到数据库有
                if (StringUtils.isNotNull(chatLists) && chatLists.size() == 1) {
                    chatList.setNoReadCount(count);
                    log.info("更新接收方消息未读数");
                    chatListService.updateChatList(chatList);
                } else {
                    chatList.setNoReadCount(count);
                    log.info("添加接收方消息未读数");
                    chatListService.insertChatList(chatList);
                }
            }
        }
        messageMap.remove(loginName);
    }


    /**
     * OnMessage 表示浏览器发消息的时候被调用
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message) {
        log.info("来自客户端{}的消息:{}", loginName, message);
        if (StringUtils.isNotBlank(message)) {
            //解析发送的报文
            JSONObject jsonObject = JSON.parseObject(message);
            //判断是否是心跳给予响应信息
            String toUserLoginName = jsonObject.getString("toUserLoginName");
            String msgType = jsonObject.getString("msg_type");
            String heartUser = jsonObject.getString("heartUser");
            if (StringUtils.isNotNull(toUserLoginName) && StringUtils.isNotEmpty(toUserLoginName)) {
                try {
                    this.sendToUser(message, toUserLoginName);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            } else if (StringUtils.isNotNull(msgType) && msgType.equals("heart") && StringUtils.isNotNull(heartUser)) {
                Session session = webSocketMap.get(heartUser).session;
                sendHeart(session, "连接成功");
            } else {
                try {
                    this.sendtoAll(message);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 发送信息给所有人
     *
     * @param message
     * @throws IOException
     */
    public void sendtoAll(String message) throws IOException {
        for (String key : webSocketMap.keySet()) {
            try {
                webSocketMap.get(key).sendAllMessage(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * OnError 表示有错误发生，比如网络断开了等等
     *
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("用户错误:" + this.loginName + ",原因:" + error.getMessage());
        error.printStackTrace();
    }

    /**
     * 实现服务器主动推送
     */
    public void sendAllMessage(String message) throws IOException {
        log.info("推送消息开始");
        this.session.getAsyncRemote().sendText(message);
        log.info("推送消息成功");
    }

    /**
     * 实现服务器主动推送
     */
    public void sendMessage(Session session, String message) throws IOException {
        log.info("推送消息开始");
        log.info("接收消息的session:" + session);
        session.getAsyncRemote().sendText(message);
        log.info("推送消息成功");
    }

    //心跳响应
    public void sendHeart(Session session, String message) {
        try {
            synchronized (session) {
                session.getBasicRemote().sendText(message);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 发送消息给指定用户
     */
    public void sendToUser(String message, String toLoginName) throws IOException {
        log.info("{}发送消息给:{}", loginName, toLoginName + "，报文:" + message);
        toUserMap.put(loginName, toLoginName);
        if (StringUtils.isNotBlank(toLoginName) && webSocketMap.containsKey(toLoginName)) {
            //将信息进行发送
            Session session = webSocketMap.get(toLoginName).session;
            log.info("接收用户的session：" + session);
            sendMessage(session, message);
            log.info("开始添加消息到集合中：" + session);
            Message turnMessage = this.turnMessage(message);
            turnMessage.setMsgStatus(ChatConstants.MSG_READ);
            List<Message> messages = messageMap.get(this.loginName);
            log.info("添加消息到集合中");
            messages.add(turnMessage);
            messageMap.put(this.loginName, messages);
            log.info("添加消息到集合完毕");
        } else {
            Message turnMessage = this.turnMessage(message);
            turnMessage.setMsgStatus(ChatConstants.MSG_NOT_READ);
            List<Message> messages = messageMap.get(this.loginName);
            messages.add(turnMessage);
            messageMap.put(this.loginName, messages);
            log.error("用户" + toLoginName + ",不在线！");
        }
    }

    //将报文转换成消息对象
    public Message turnMessage(String message) {
        JSONObject jsonObject = JSON.parseObject(message);
        String fromUserName = jsonObject.getString("fromUserName");
        String fromAvatar = jsonObject.getString("fromAvatar");
        String toUserName = jsonObject.getString("toUserName");
        String toUserLoginName = jsonObject.getString("toUserLoginName");
        String content = jsonObject.getString("content");
        String voiceTime = jsonObject.getString("voiceTime");
        Date createTime = DateUtils.parseDate(jsonObject.getString("createTime"));
        int msgType = Integer.parseInt(jsonObject.getString("msgType"));
        //存储消息到数据库
        Message chatRet = new Message();
        chatRet.setFromUserLoginName(this.loginName);
        chatRet.setFromAvatar(fromAvatar);
        chatRet.setFromUserName(fromUserName);
        chatRet.setToUserLoginName(toUserLoginName);
        chatRet.setToUserName(toUserName);
        chatRet.setContent(content);
        chatRet.setCreateTime(createTime);
        if (StringUtils.isNotNull(voiceTime) && StringUtils.isNotEmpty(voiceTime)) {
            Long voicetime = Long.valueOf(voiceTime);
            chatRet.setVoiceTime(voicetime);
        }
        chatRet.setMsgType(msgType);
        return chatRet;
    }


    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        WebSocketServer.onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        WebSocketServer.onlineCount--;
    }
}
